/**
 *
 */
package uk.co.hs.web.validator;

import java.io.Serializable;
import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Service;

import uk.co.hs.domain.web.WebPasswordHistory;
import uk.co.hs.domain.web.WebUser;
import uk.co.hs.service.admin.interfaces.WebUserServiceInterface;

/**
 * PasswordPolicyValidator.
 */
@Service("hs_passwordPolicyValidator")
public class PasswordPolicyValidator implements Serializable
{
  private static final long serialVersionUID = 1L;

  @Value("${password.policy.must.contain.lowercase}")
  private boolean mMustContainLowerCase;
  @Value("${password.policy.must.contain.uppercase}")
  private boolean mMustContainUpperCase;
  @Value("${password.policy.must.contain.digit}")
  private boolean mMustContainDigit;
  @Value("${password.policy.minimum.length}")
  private int mMinimumLength;
  @Value("${password.policy.history.expiry}")
  private int mHistoryExpiry;

  @Autowired(required = true)
  @Qualifier("hs_webUserService")
  private WebUserServiceInterface mUserService;

  @Autowired(required = true)
  @Qualifier("messageSource")
  private MessageSource mMessageSource;

  /**
   * Validates a password.
   *
   * Validation based on the system properties.
   *
   * @param aPassword Password
   * @return String containing an error or null (if no error).
   */
  public String validatePassword(String aPassword,
                                 String aUsername)
  {
    // Password Required
    if (aPassword == null || aPassword.isEmpty())
    {
      return mMessageSource.getMessage("errors.required", new String[] { "Password" }, Locale.getDefault());
    }

    // Password Length
    if (aPassword.length() < mMinimumLength)
    {
      return mMessageSource.getMessage("errors.password.length", new String[] { "Password", "" + mMinimumLength,
          "" + aPassword.length() }, Locale.getDefault());
    }

    // Check Password Content
    char[] passwordCharacters = aPassword.toCharArray();
    char currentChar;
    int numberOfChars = passwordCharacters.length;
    boolean containsDigit = false, containsLowerCaseLetter = false, containsUpperCaseLetter = false;
    for (int charIndex = 0; charIndex < numberOfChars; charIndex++)
    {
      currentChar = passwordCharacters[charIndex];
      if (Character.isDigit(currentChar))
      {
        containsDigit = true;
      }
      else if (Character.isLetter(currentChar))
      {
        if (Character.isLowerCase(currentChar))
        {
          containsLowerCaseLetter = true;
        }
        else if (Character.isUpperCase(currentChar))
        {
          containsUpperCaseLetter = true;
        }
      }
    }

    if ((!containsDigit && mMustContainDigit) || (!containsLowerCaseLetter && mMustContainLowerCase)
        || (!containsUpperCaseLetter && mMustContainUpperCase))
    {
      return mMessageSource.getMessage("errors.password.invalid",
                                       new String[] { "Password", getPasswordPolicyString() }, Locale.getDefault());
    }

    // Check Password isn't in the last x historic ones for this user
    if (aUsername != null && !aUsername.equals("null"))
    {
      WebUser userFromDb = mUserService.getWebUserIncludingPasswordHistory(aUsername);
      if (userFromDb != null)
      {
        String encodedPassword = mUserService.encodePassword(aPassword);
        int historicPasswordsChecked = 0;
        for (WebPasswordHistory current : userFromDb.getPasswordHistory())
        {
          if (current.getPassword().equals(encodedPassword))
          {
            return mMessageSource.getMessage("errors.password.equals.historic", new String[] { "Password" },
                                             Locale.getDefault());
          }
          historicPasswordsChecked++;
          if (historicPasswordsChecked > mHistoryExpiry)
          {
            break;
          }
        }
      }
    }
    return null;
  }

  /**
   * Validates a password confirmation.
   *
   * @param aPasswordConfirmation Password Confirmation
   * @param aPassword Password
   * @return String containing an error or null (if no error).
   */
  public String validatePasswordConfirm(String aPasswordConfirmation,
                                        String aPassword)
  {
    boolean passwordsDontMatch = false;
    if (aPassword != null && !aPassword.isEmpty())
    {
      if (aPasswordConfirmation == null || aPasswordConfirmation.isEmpty() || !aPassword.equals(aPasswordConfirmation))
      {
        passwordsDontMatch = true;
      }
    }

    if (passwordsDontMatch)
    {
      return mMessageSource.getMessage("errors.password.confirm.match", new String[] {}, Locale.getDefault());
    }

    return null;
  }

  /**
   * Validates a password.
   *
   * Validation based on the system properties.
   *
   * @param aUserName Username.
   * @param aPassword Password
   * @return String containing an error or null (if no error).
   */
  public String validateExistingPassword(String aUserName,
                                         String aPassword)
  {
    // Password Required
    if (aPassword == null || aPassword.isEmpty())
    {
      return mMessageSource.getMessage("errors.required", new String[] { "Password" }, Locale.getDefault());
    }

    // Check the encoded password in the DB matches the password entered (after encoding it)
    if (!mUserService.passwordsMatch(aUserName, aPassword))
    {
      return mMessageSource.getMessage("errors.existing.password.incorrect", new String[] {}, Locale.getDefault());
    }

    return null;
  }

  private String getPasswordPolicyString()
  {
    StringBuilder passwordPolicyString = new StringBuilder();

    // Contain lowercase?
    if (mMustContainLowerCase)
    {
      passwordPolicyString.append(mMessageSource.getMessage("errors.password.lowercase", new String[] {},
                                                            Locale.getDefault()));
    }

    // Contain uppercase?
    if (mMustContainUpperCase)
    {
      passwordPolicyString.append(mMessageSource.getMessage("errors.password.uppercase", new String[] {},
                                                            Locale.getDefault()));
    }

    // Contain digit?
    if (mMustContainDigit)
    {
      passwordPolicyString.append(mMessageSource.getMessage("errors.password.digit", new String[] {},
                                                            Locale.getDefault()));
    }

    return passwordPolicyString.toString();
  }
}
